using System.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Reflection;
using System.Diagnostics.CodeAnalysis; // for FxCop warning suppression
using System.Text.RegularExpressions;
using System.Text;

namespace PDO.DataPersistence.Core
{
    /// <summary>
    /// Helper classes and utilities for interacting with objects via .NET Reflection
    /// </summary>
    /// 
    public static class ReflectionHelper
    {
        #region Public static methods

        const string _staticPropertyNamePrefix = "static:";
        private static List<string> _defaultTypeNameTemplateList;


        /// <summary>
        /// Is the provided property path referring to a static property?
        /// </summary>
        /// <param name="propertyPath">property path</param>
        /// <returns>true if referring to a static property, false otherwise.</returns>
        public static bool IsStaticPropertyPath(string propertyPath)
        {
            return propertyPath.StartsWith(_staticPropertyNamePrefix, StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Get property path for static property reference, and name of referenced type as out param.
        /// </summary>
        /// <param name="staticPropertyPath">static property path</param>
        /// <param name="typeName">name of referenced type</param>
        /// <returns>property path</returns>
        public static string GetStaticPropertyPath(string staticPropertyPath, out string typeName)
        {
            int startPos = staticPropertyPath.IndexOf(":", StringComparison.InvariantCultureIgnoreCase);
            int endPos = staticPropertyPath.IndexOf(".", startPos + 1, StringComparison.InvariantCultureIgnoreCase);

            typeName = staticPropertyPath.Substring(startPos + 1, endPos - startPos - 1).Trim();
            return staticPropertyPath.Substring(endPos + 1);

        }


        /// <summary>
        /// Evaluate a boolean expression against a provided data context PersistableDataObject.
        /// Example expression:
        ///     string cond = "((FirstName=\"John\" && LastName=\"Doe\") && Gender.Abbreviation=\"M\") && ((Address.Address1=\"123\") && (Address.City=\"Madison\"))";
        ///  or
        ///     string cond ="(ContactList[FirstName=\"Marilyn\"] != \"null\") && Gender.Abbreviation=\"M\" ";
        /// </summary>
        /// <param name="pdoSource">data context</param>
        /// <param name="expression">boolean expression</param>
        /// <returns>true if expression evaluates to true, false otherwise.</returns>
        public static bool EvaluateBool(PersistableDataObject pdoSource, string expression)
        {
            
            bool result = true;

            if (!bool.TryParse(PDOExpressionEvaluator.Evaluate(pdoSource, expression), out result))
            {
                result = false;
            }
            return result;



            // =================================================
            // DEPRECATED - TO BE REMOVED 

            //bool recurse = false;

            //ConditionalTokenizer et = new ConditionalTokenizer(expression);

            //et.MoveNext(); // get first token

            //while (!et.IsComplete)
            //{
            //    if (!recurse)
            //    {
            //        // first token 
            //        result &= EvaluateBool(pdoSource, et.Argument1);
            //    }

            //    if (et.Operator.Length > 0)
            //    {
            //        bool b2 = EvaluateBool(pdoSource, et.Argument2);
            //        if (et.Operator.Contains("&"))
            //        {
            //            result &= b2;
            //        }
            //        else if (et.Operator.Contains("|"))
            //        {
            //            result |= b2;
            //        }
            //    }

            //    recurse = true;
                
            //    et.MoveNext();
            //}

            //if (recurse)
            //{
            //    return result;
            //}

            //expression = et.Argument1; // Argument1 has white space and redundant parenthese removed.

            //// At this point, we should have a single expression of form "<exp1> && <exp2>" or "<exp1> || <exp2>".

            //if (expression.Contains("&"))
            //{
            //    string[] delims = { "&" };
            //    string[] spec = expression.Split(delims, StringSplitOptions.RemoveEmptyEntries);

            //    bool b1 = EvaluateBool(pdoSource, spec[0]);
            //    bool b2 = EvaluateBool(pdoSource, spec[1]);
            //    return b1 && b2;
            //}

            //if (expression.Contains("|"))
            //{
            //    string[] delims = { "|" };
            //    string[] spec = expression.Split(delims, StringSplitOptions.RemoveEmptyEntries);

            //    bool b1 = EvaluateBool(pdoSource, spec[0]);
            //    bool b2 = EvaluateBool(pdoSource, spec[1]);
            //    return b1 || b2;
            //}


            //// At this point we should have a simple equality expression of form A == B or C != D.

            //bool negate = expression.Contains('!');

            //string key;
            //string val;
            //try
            //{
            //    string[] delims = { "!", "=" };
            //    string[] spec = expression.Split(delims, StringSplitOptions.RemoveEmptyEntries);

            //    string a1 = spec[0].Trim();
            //    string a2 = spec[1].Trim();

            //    if (a1.Contains('"'))
            //    {
            //        val = a1.Substring(1, a1.Length - 2);
            //        key = a2;
            //    }
            //    else if (a2.Contains('"'))
            //    {
            //        val = a2.Substring(1, a2.Length - 2);
            //        key = a1;
            //    }
            //    else
            //    {
            //        val = a2;
            //        key = a1;
            //    }
            //}

            //catch (Exception ex)
            //{
            //    throw new InvalidOperationException("Error parsing boolean expression.", ex);
            //}

            //string testValue = ReflectionHelper.GetObjectPropertyValue(pdoSource, key) as string;
            //result = (null != testValue && testValue.Equals(val));

            //if (negate)
            //{
            //    result = !result;
            //}

            //return result;

            // =================================================
        }




        /// <summary>
        /// Return leaf object from root object via a navigation path.
        /// </summary>
        /// <param name="obj">source object</param>
        /// <param name="propertyPath">property path</param>
        /// <returns>object</returns>
        static public object GetLeafObject(object obj, string propertyPath)
        {
            object leafObject;
            GetPropertyInfo(obj, propertyPath, out leafObject);
            return leafObject;
        }


        /// <summary>
        /// Return property information for property defined with a navigation path.
        /// </summary>
        /// <param name="obj">source object</param>
        /// <param name="propertyPath">property path</param>
        /// <returns>PropertyInfo</returns>
        static public PropertyInfo GetPropertyInfo(object obj, string propertyPath)
        {
            object leafObject;
            return GetPropertyInfo(obj, propertyPath, out leafObject);
        }


        /// <summary>
        /// Return property information for property defined with a navigation path.
        /// </summary>
        /// <param name="obj">source object</param>
        /// <param name="propertyPath">property path</param>
        /// <param name="leafObject">leaf object from end of property path</param>
        /// <param name="propertyName">leaf property name from end of property path</param>
        /// <returns>PropertyInfo</returns>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#")]
        static public PropertyInfo GetPropertyInfo(object obj, string propertyPath,
            out object leafObject, out string propertyName)
        {
            // Initialize
            propertyName = null;
            leafObject = null;

            if (null == obj)
            {
                throw new ArgumentOutOfRangeException("obj", "Target object for GetPropertyInfo is null.");
            }

            if (null == propertyPath)
            {
                throw new ArgumentOutOfRangeException("propertyPath", "propertyPath for GetPropertyInfo is null.");
            }

            int dotIndex = propertyPath.IndexOf(".", StringComparison.Ordinal);

            if (dotIndex != -1) //if this string contains a . then it is not the leaf property 
            {
                // Split the string 
                string thisProperty = propertyPath.Substring(0, dotIndex); // path for this node 
                string remainderPath = propertyPath.Substring(dotIndex + 1); // pass the remainder to recursive call 


                if (null != obj.GetType().GetProperty(thisProperty))
                {
                    return GetPropertyInfo(obj.GetType().GetProperty(thisProperty).GetValue(obj, null),
                        remainderPath, out leafObject, out propertyName);
                }
                else
                {
                    return null;
                }

            }
            else // get the value for the property 
            {
                leafObject = obj;
                propertyName = propertyPath;
                return obj.GetType().GetProperty(propertyPath);
            }

        }


        /// <summary>
        /// Return property information for property defined with a navigation path.
        /// </summary>
        /// <param name="obj">source object</param>
        /// <param name="propertyPath">property path</param>
        /// <param name="leafObject">leaf object of navigation path</param>
        /// <returns>PropertyInfo</returns>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
        static public PropertyInfo GetPropertyInfo(object obj, string propertyPath, out object leafObject)
        {
            leafObject = null;

            if (null == obj)
            {
                throw new ArgumentOutOfRangeException("obj", "Target object for GetPropertyInfo is null.");
            }

            if (null == propertyPath)
            {
                throw new ArgumentOutOfRangeException("propertyPath", "propertyPath for GetPropertyInfo is null.");
            }

            int dotIndex = propertyPath.IndexOf(".", StringComparison.Ordinal);

            if (dotIndex != -1) //if this string contains a "." then it is not the leaf property 
            {
                // Split the string 
                string thisProperty = propertyPath.Substring(0, dotIndex); // path for this node 
                string remainderPath = propertyPath.Substring(dotIndex + 1); // pass the remainder to recursive call 

                object currentValue = SimpleGetProperyValue(obj, thisProperty);
                if (null != currentValue)
                {
                    return GetPropertyInfo(currentValue, remainderPath, out leafObject);
                }
                else
                {
                    return null;
                }

            }
            else //get the value for the property 
            {
                leafObject = obj;
                return obj.GetType().GetProperty(propertyPath);
            }
        }



        /// <summary>
        /// Get simple property value from provided object.  Handles array reference resolution for simple integer array indices.
        /// </summary>
        /// <param name="obj">source object</param>
        /// <param name="simplePropertyName">simple property name</param>
        /// <returns>property value</returns>
        static private object SimpleGetProperyValue(object obj, string simplePropertyName)
        {
            // Check for array index reference.
            int startPos = simplePropertyName.IndexOf('[');
            if (startPos > -1)
            {
                int endPos = simplePropertyName.IndexOf(']');
                if (-1 == endPos)
                {
                    throw new ArgumentException("Missing closing \"]\" in array reference.", simplePropertyName);
                }

                string index = simplePropertyName.Substring(startPos + 1, (endPos - startPos) - 1);

                string arrayName = simplePropertyName.Substring(0, startPos);

                IList objArray = obj.GetType().GetProperty(arrayName).GetValue(obj, null) as IList;
                if (null == objArray)
                {
                    throw new ArgumentException("Invalid reference to array.", simplePropertyName);
                }

                IPersistableDataObjectList pdoList = objArray as IPersistableDataObjectList;
                if (null != pdoList)
                {
                    return pdoList[index];
                }
                else
                {
                    int arrayIndex = Convert.ToInt32(index, System.Globalization.CultureInfo.InvariantCulture);

                    if (arrayIndex < 0)
                    {
                        throw new ArgumentException("Array index cannot be less than zero.", simplePropertyName);
                    }



                    if (arrayIndex > objArray.Count - 1)
                    {
                        throw new ArgumentException("Array index greater than item content.", simplePropertyName);
                    }

                    return objArray[arrayIndex];
                }

            }
            else
            {
                object retValue = null;
                try
                {
                    retValue = obj.GetType().GetProperty(simplePropertyName).GetValue(obj, null);
                }
                catch (Exception ex)
                {
                    int foo = 2;
                }
                return retValue;
            }
        }


        static public object GetPropertyPathValue(object obj, string propertyPath)
        {
            if (null == obj)
            {
                throw new ArgumentOutOfRangeException("obj", "Target object for GetPropertyInfo is null.");
            }

            if (string.IsNullOrEmpty(propertyPath))
            {
                return obj;
            }


            object leafObject = null;
            PropertyPathTokenizer et = new PropertyPathTokenizer(propertyPath);

            while (et.MoveNext())
            {
                if (et.IsMethodCall)
                {
                    leafObject = CallMethod(obj, et.MethodName, et.ParameterList.ToArray());
                }
                else
                {
                    leafObject = SimpleGetProperyValue(obj, et.PropertyName);
                }
                obj = leafObject;
            }
            return leafObject;
        }


        /// <summary>
        /// Tests if property Get supported by object.
        /// </summary>
        /// <param name="obj">object</param>
        /// <param name="propertyName">property name</param>
        /// <returns>true if property supported by object, false otherwise</returns>
        static public bool IsProperty(object obj, string propertyName)
        {
            PropertyInfo pi = GetPropertyInfo(obj, propertyName);

            MethodInfo mi = null;
            if (pi != null)
            {
                mi = pi.GetGetMethod();
            }

            return (mi != null);
        }


        /// <summary>
        /// Tests if method supported by object.
        /// </summary>
        /// <param name="obj">object</param>
        /// <param name="methodName">method name</param>
        /// <returns>true if method supported by object, false otherwise</returns>
        static public bool IsMethod(object obj, string methodName)
        {
            if (null == obj)
                return false;

            Type type = obj.GetType();
            MethodInfo mi;
            mi = type.GetMethod(methodName);

            return (mi != null);
        }


        /// <summary>
        /// Invoke specified method on object.
        /// </summary>
        /// <param name="obj">object on which to invoke method</param>
        /// <param name="methodName">name of method to invoke</param>
        /// <returns>return value of invoked method</returns>
        static public object InvokeMethod(object obj, string methodName)
        {
            return InvokeMethod(obj, methodName, null);
        }


        /// <summary>
        /// Invoke specified method on object.
        /// </summary>
        /// <param name="obj">object on which to invoke method</param>
        /// <param name="methodName">name of method to invoke</param>
        /// <returns>return value of invoked method</returns>
        static public object InvokeMethod(object obj, string methodName, object[] args)
        {
            if (null == obj)
            {
                throw new ArgumentOutOfRangeException("obj", "Invoke target object is null.");
            }

            if (null == methodName)
            {
                throw new ArgumentOutOfRangeException("methodName", "Method name missing.");
            }

            Type type = obj.GetType();
            MethodInfo mi;
            mi = type.GetMethod(methodName);
            return mi.Invoke(obj, args);
        }


        /// <summary>
        /// Invoked a method on an assembly via reflection.  
        /// Used this approach as opposed to directly calling to avoid circular dependencies.
        /// </summary>
        static public object InvokeMethod(string assemblyName, string typeName,
            string methodName, object[] args)
        {
            Assembly pmAssembly = Assembly.LoadFrom(assemblyName);

            object pm = pmAssembly.CreateInstance(typeName);

            MethodInfo mi = pm.GetType().GetMethod(methodName);
            return mi.Invoke(pm, args);
        }



        /// <summary>
        /// Return field value from object.
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="fieldName">field name</param>
        /// <returns>value object or null</returns>
        static public object GetObjectFieldValue(object source, string fieldName)
        {
            FieldInfo fi = source.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

            if (null == fi)
            {
                return null;
            }
            else
            {
                return fi.GetValue(source);
            }
        }
        /// <summary>
        /// List of default type name templates to search as needed to disambiguate type names.
        /// </summary>
        public static List<string> DefaultTypeNameTemplateList
        {
            get
            {
                if (null == _defaultTypeNameTemplateList)
                {
                    _defaultTypeNameTemplateList = new List<string>();
                }
                return _defaultTypeNameTemplateList;
            }

            set { _defaultTypeNameTemplateList = value; }
        }


        /// <summary>
        /// Return property value from static property on provided type if available.
        /// </summary>
        /// <param name="typeName">name of type</param>
        /// <param name="propertyName">static property name</param>
        /// <returns>property value</returns>
        static public object GetStaticPropertyValue(string typeName, string propertyName)
        {
            return GetStaticPropertyValue(typeName, propertyName, null);
        }

        /// <summary>
        /// Return property value from static property on provided type if available.
        /// </summary>
        /// <param name="typeName">name of type</param>
        /// <param name="propertyName">static property name</param>
        /// <param name="defaultValue">default value of static property not found.</param>
        /// <returns>property value</returns>
        static public object GetStaticPropertyValue(string typeName, string propertyName,
                                object defaultValue)
        {
            Type staticType;
            return GetStaticPropertyValue(typeName, propertyName, defaultValue, out staticType);
        }


        /// <summary>
        /// Return property value from static property on provided type if available.
        /// </summary>
        /// <param name="typeName">name of type</param>
        /// <param name="propertyName">static property name</param>
        /// <param name="defaultValue">default value of static property not found.</param>
        /// <param name="staticType"> out Type of named property</param>
        /// <returns>property value</returns>
        static public object GetStaticPropertyValue(string typeName, string propertyName,
                                object defaultValue, out Type staticType)
        {

            staticType = null;
            if (typeName.Contains(".")) // treat as a fully qualified type name
            {
                staticType = Type.GetType(typeName);
            }
            else // try applying default assembly name templates to provided type name
            {
                foreach (string template in _defaultTypeNameTemplateList)
                {
                    string fullTypeName = string.Format(template, typeName);
                    Type type = Type.GetType(fullTypeName);
                    if (null != type)
                    {
                        if (staticType != null)
                        {
                            throw new ArgumentException("Ambiguous type name: " + typeName);
                        }
                        staticType = type;
                    }
                }
            }


            object ret = null;
            if (null != staticType)
            {
                ret = GetPropertyPathValue(staticType, propertyName);
            }
            else
            {
                throw new ArgumentException("Type not found: " + typeName);
            }

            if (ret == null)
            {
                ret = defaultValue;
            }

            return ret;
        }



        /// <summary>
        /// Return property value from object if available
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="propertyName">property</param>
        /// <param name="defaultValue">default value returned if requested property is null</param>
        /// <returns>value object or null</returns>
        static public object GetObjectPropertyValue(object source,
            string propertyName,
            object defaultValue)
        {
            object ret = GetObjectPropertyValue(source, propertyName);

            if (ret == null)
            {
                ret = defaultValue;
            }

            return ret;
        }


        /// <summary>
        /// Return property value from object if available
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="propertyName">property</param>
        /// <param name="defaultValue">default value returned if requested property is null</param>
        /// <param name="valueType">Type of returned object</param>
        /// <returns>value object or null</returns>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#")]
        static public object GetObjectPropertyValue(object source,
                                                    string propertyName,
                                                    object defaultValue, out Type valueType)
        {
            object ret = GetObjectPropertyValue(source, propertyName, out valueType);

            if (ret == null)
            {
                ret = defaultValue;
            }

            return ret;
        }


        /// <summary>
        /// Return property value from object if available
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="propertyName">property</param>
        /// <param name="valueType">Type of returned object</param>
        /// <returns>value object or null</returns>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
        static public object GetObjectPropertyValue(object source, string propertyName,
                                                    out Type valueType)
        {
            object ret = null;
            object leafObject;

            PropertyInfo pi = GetPropertyInfo(source, propertyName, out leafObject);

            if (pi != null)
            {
                ret = GetObjectPropertyValue(leafObject, pi);
                valueType = pi.PropertyType;
            }
            else
            {
                valueType = null;
            }

            return ret;
        }

        /// <summary>
        /// Return property value from object 
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="pi">PropertyInfo</param>
        /// <returns>value object or null</returns>
        static public object GetObjectPropertyValue(object source, PropertyInfo pi)
        {
            object ret = null;

            MethodInfo mi = null;
            mi = pi.GetGetMethod();

            if (mi != null)
            {
                ParameterInfo[] parmInfo = pi.GetIndexParameters();

                if (parmInfo.Length > 0) // this property is actually an indexer
                {
                    return "[indexer]";
                }
                else
                {
                    object[] argValues = { };
                    ret = mi.Invoke(source, argValues);
                }
            }

            return ret;
        }


        /// <summary>
        /// Return property value from object if available
        /// </summary>
        /// <param name="source">data source object</param>
        /// <param name="propertyName">property</param>
        /// <returns>value object or null</returns>
        static public object GetObjectPropertyValue(object source, string propertyName)
        {
            Type valueType;
            return GetObjectPropertyValue(source, propertyName, out valueType);
        }

        public static void CopyObjectPropertyValue(object source, string sourcePropertyName, object target, string targetPropertyName)
        {
            Type valueType;
            object value = GetObjectPropertyValue(source, sourcePropertyName, out valueType);
            if (valueType != null)
            {
                SetObjectPropertyValue(target, targetPropertyName, value);
            }
        }


        /// <summary>
        /// Convert provided value to appropriate type for target property.
        /// </summary>
        /// <param name="targetPropertyInfo">PropertyInfo for target object</param>
        /// <param name="dataValue">value to set</param>
        /// <param name="convertedValue">converted value</param>
        /// <returns>true for success, false otherwise.</returns>
        public static object ConvertToTargetType(string propertyTypeFullName, bool isNullable, object dataValue)
        {

            if (isNullable && dataValue == null)
            {
                return null;
            }

            object convertedValue = null;
            switch (propertyTypeFullName)
            {
                case "SYSTEM.GUID":
                    if (null == dataValue)
                        convertedValue = null;  // runtime converts to Guid.Empty where necessary.
                    else
                        convertedValue = new Guid(dataValue.ToString());
                    break;
                case "SYSTEM.BOOLEAN":
                    convertedValue = System.Convert.ToBoolean(dataValue);
                    break;
                case "BOOLEAN":
                    convertedValue = System.Convert.ToBoolean(dataValue);
                    break;
                case "BOOL":
                    convertedValue = System.Convert.ToBoolean(dataValue);
                    break;
                case "SYSTEM.BYTE":
                    convertedValue = System.Convert.ToByte(dataValue);
                    break;
                case "BYTE":
                    convertedValue = System.Convert.ToByte(dataValue);
                    break;
                case "SYSTEM.CHAR":
                    convertedValue = System.Convert.ToChar(dataValue);
                    break;
                case "CHAR":
                    convertedValue = System.Convert.ToChar(dataValue);
                    break;
                case "SYSTEM.DATE":
                    convertedValue = System.Convert.ToDateTime(dataValue);
                    break;
                case "DATE":
                    convertedValue = System.Convert.ToDateTime(dataValue);
                    break;
                case "SYSTEM.DATETIME":
                    convertedValue = System.Convert.ToDateTime(dataValue);
                    break;
                case "DATETIME":
                    convertedValue = System.Convert.ToDateTime(dataValue);
                    break;
                case "SYSTEM.DECIMAL":
                    convertedValue = System.Convert.ToDecimal(dataValue);
                    break;
                case "DECIMAL":
                    convertedValue = System.Convert.ToDecimal(dataValue);
                    break;
                case "SYSTEM.DOUBLE":
                    convertedValue = System.Convert.ToDouble(dataValue);
                    break;
                case "DOUBLE":
                    convertedValue = System.Convert.ToDouble(dataValue);
                    break;
                case "SYSTEM.INT16":
                    convertedValue = System.Convert.ToInt16(dataValue);
                    break;
                case "INT16":
                    convertedValue = System.Convert.ToInt16(dataValue);
                    break;
                case "SYSTEM.INT32":
                    convertedValue = System.Convert.ToInt32(dataValue);
                    break;
                case "INT32":
                    convertedValue = System.Convert.ToInt32(dataValue);
                    break;
                case "SYSTEM.INT64":
                    convertedValue = System.Convert.ToInt64(dataValue);
                    break;
                case "INT64":
                    convertedValue = System.Convert.ToInt64(dataValue);
                    break;
                case "SYSTEM.INTEGER":
                    convertedValue = System.Convert.ToInt32(dataValue);
                    break;
                case "INTEGER":
                    convertedValue = System.Convert.ToInt32(dataValue);
                    break;
                case "INT":
                    convertedValue = System.Convert.ToInt32(dataValue);
                    break;
                case "SYSTEM.LONG":
                    convertedValue = System.Convert.ToInt64(dataValue);
                    break;
                case "LONG":
                    convertedValue = System.Convert.ToInt64(dataValue);
                    break;
                case "SYSTEM.SBYTE":
                    convertedValue = System.Convert.ToSByte(dataValue);
                    break;
                case "SBYTE":
                    convertedValue = System.Convert.ToSByte(dataValue);
                    break;
                case "SYSTEM.SHORT":
                    convertedValue = System.Convert.ToInt16(dataValue);
                    break;
                case "SHORT":
                    convertedValue = System.Convert.ToInt16(dataValue);
                    break;
                case "SYSTEM.SINGLE":
                    convertedValue = System.Convert.ToSingle(dataValue);
                    break;
                case "SINGLE":
                    convertedValue = System.Convert.ToSingle(dataValue);
                    break;
                case "FLOAT":
                    convertedValue = System.Convert.ToSingle(dataValue);
                    break;
                case "SYSTEM.STRING":
                    convertedValue = System.Convert.ToString(dataValue);
                    break;
                case "STRING":
                    convertedValue = System.Convert.ToString(dataValue);
                    break;
                case "SYSTEM.UINT16":
                    convertedValue = System.Convert.ToUInt16(dataValue);
                    break;
                case "UINT16":
                    convertedValue = System.Convert.ToUInt16(dataValue);
                    break;
                case "SYSTEM.UINT32":
                    convertedValue = System.Convert.ToUInt32(dataValue);
                    break;
                case "UINT32":
                    convertedValue = System.Convert.ToUInt32(dataValue);
                    break;
                case "SYSTEM.UINT64":
                    convertedValue = System.Convert.ToUInt64(dataValue);
                    break;
                case "UINT64":
                    convertedValue = System.Convert.ToUInt64(dataValue);
                    break;
                case "SYSTEM.UINTPTR":
                    convertedValue = (System.UIntPtr)dataValue;
                    break;
                case "UINTPTR":
                    convertedValue = (System.UIntPtr)dataValue;
                    break;
                case "SYSTEM.INTPTR":
                    convertedValue = (System.IntPtr)dataValue;
                    break;
                case "INTPTR":
                    convertedValue = (System.IntPtr)dataValue;
                    break;
                case "SYSTEM.UINTEGER":
                    convertedValue = System.Convert.ToUInt32(dataValue);
                    break;
                case "UINTEGER":
                    convertedValue = System.Convert.ToUInt32(dataValue);
                    break;
                case "UINT":
                    convertedValue = System.Convert.ToUInt32(dataValue);
                    break;
                case "SYSTEM.ULONG":
                    convertedValue = System.Convert.ToUInt64(dataValue);
                    break;
                case "ULONG":
                    convertedValue = System.Convert.ToUInt64(dataValue);
                    break;
                case "SYSTEM.USHORT":
                    convertedValue = System.Convert.ToUInt16(dataValue);
                    break;
                case "USHORT":
                    convertedValue = System.Convert.ToUInt16(dataValue);
                    break;
                case "SYSTEM.OBJECT":
                    convertedValue = dataValue;
                    break;
                case "SYSTEM.BYTE[]":
                    convertedValue = dataValue;
                    break;
                case "OBJECT":
                    convertedValue = dataValue;
                    break;
                default:
                    throw new ArgumentException("Unable to convert to provided type of " + propertyTypeFullName + ".");
            }
            return convertedValue;
        }


        /// <summary>
        /// Set value of property exposed by an object.  
        /// Attempts to coerce provided value to match property on target object, throwing ArgumentException if corecion fails.
        /// </summary>
        /// <param name="source">target object</param>
        /// <param name="propertyName">property</param>
        /// <param name="value">value to set</param>
        public static void SetObjectPropertyValue(object target, string propertyName, object value)
        {
            object leafObject;
            PropertyInfo pi = GetPropertyInfo(target, propertyName, out leafObject);

            string propertyTypeFullName = pi.PropertyType.FullName.ToUpper();

            bool isNullable = false;
            if (pi.PropertyType.IsGenericType)
            {
                isNullable = pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
                propertyTypeFullName = pi.PropertyType.GetGenericArguments()[0].FullName.ToUpper();
            }

            object convertedValue = ConvertToTargetType(propertyTypeFullName, isNullable, value);

            pi.SetValue(leafObject, convertedValue, null);
        }


        /// <summary>
        /// Set value of property exposed by an object.  
        /// Does *not* attempt any coercion of provided value to match property on target object.
        /// </summary>
        /// <param name="source">target object</param>
        /// <param name="propertyName">property</param>
        /// <param name="value">value to set</param>
        /// <returns>true if successfully found property and set value, false otherwise</returns>
        public static bool SetObjectPropertyValueDirect(object target, string propertyName, object value)
        {
            object leafObject;
            PropertyInfo pi = GetPropertyInfo(target, propertyName, out leafObject);

            string propertyTypeFullName = pi.PropertyType.FullName.ToUpper();

            if (pi.PropertyType.IsGenericType)
            {
                propertyTypeFullName = pi.PropertyType.GetGenericArguments()[0].FullName.ToUpper();
            }

            pi.SetValue(leafObject, value, null);
            return true;
        }


        static public MethodInfo GetMethodInfo(object obj, string methodPath, out object leafObject)
        {
            leafObject = null;

            if (null == obj)
            {
                throw new ArgumentOutOfRangeException("obj", "Target object for GetMethodInfo is null.");
            }

            if (null == methodPath)
            {
                throw new ArgumentOutOfRangeException("propertyPath", "methodPath for GetMethodInfo is null.");
            }

            int dotIndex = methodPath.IndexOf(".", StringComparison.Ordinal);

            if (dotIndex != -1) //if this string contains a "." then it is not the leaf property 
            {
                // Split the string 
                string thisMethod = methodPath.Substring(0, dotIndex); // path for this node 
                string remainderPath = methodPath.Substring(dotIndex + 1); // pass the remainder to recursive call 

                object currentValue = SimpleGetProperyValue(obj, thisMethod);
                if (null != currentValue)
                {
                    return GetMethodInfo(currentValue, remainderPath, out leafObject);
                }
                else
                {
                    return null;
                }

            }
            else //get the value for the method
            {
                leafObject = obj;
                return obj.GetType().GetMethod(methodPath);
            }
        }



        /// <summary>
        /// Call a method by name on provided target object.
        /// </summary>
        /// <param name="target">Object on which to call method.  </param>
        /// <param name="methodName">Method name. May be a dotted property path.</param>
        /// <param name="returnValue">Value returned from method call</param>
        /// <param name="parameterValues">Method arguments</param>
        /// <returns>object return of method call</returns>
        public static object CallMethod(object target, string methodName, params object[] parameterValues)
        {
            // Remove any trailing "()" string
            methodName = methodName.Replace("()", string.Empty);

            object leafObject;
            MethodInfo mi = GetMethodInfo(target, methodName, out leafObject);

            return mi.Invoke(leafObject, parameterValues);
        }


        public static void Copy(object source, object destination)
        {
            if (null == source)
            {
                throw new ArgumentOutOfRangeException("source", "source object for Copy is null.");
            }

            if (null == destination)
            {
                throw new ArgumentOutOfRangeException("destination", "destination object for Copy is null.");
            }

            FieldInfo[] fields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            foreach (FieldInfo f in fields)
            {
                f.SetValue(destination, f.GetValue(source));
            }
        }

        public static object FindListItem(IList list, string property, object value)
        {
            return FindListItem(list, property, value, false);
        }

        public static object FindListItem(IList list, string property, object value, bool checkDuplicates)
        {
            object selectedItem = null;
            foreach (object item in list)
            {
                if (value.ToString() == ReflectionHelper.GetObjectPropertyValue(item, property).ToString())
                {
                    if (checkDuplicates && selectedItem != null)
                    {
                        string msg = string.Format(System.Globalization.CultureInfo.InvariantCulture,
                            "Multiple items found with same key property value; {0} = {1}.",
                            property, value.ToString());
                        throw new ArgumentException(msg);
                    }

                    selectedItem = item;

                    if (!checkDuplicates)
                    {
                        break;
                    }
                }
            }
            return selectedItem;

        }


        /// <summary>
        /// Get the attribute of type T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns>null if the attribute is not found.</returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static T GetAttribute<T>(Type t)
        {
            object[] attributes = t.GetCustomAttributes(typeof(T), true);

            if (attributes.Length < 1)
            {
                return default(T);
            }

            return (T)attributes[0];
        }


        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static T GetAttribute<T>(PropertyInfo pi)
        {
            T attr = default(T);
            object[] attrs = pi.GetCustomAttributes(typeof(T), false);
            if (attrs.Length == 1)
            {
                attr = (T)attrs[0];
            }
            return attr;
        }

        /// <summary>
        /// Return true if provided property is browsable.
        /// </summary>
        /// <param name="pi">property info</param>
        /// <returns>true if browsable, false otherwise</returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static bool IsBrowsableX(PropertyInfo pi)
        {
            bool ret = true;
            object[] attrs;

            // Get array of BrowsableAttribute attributes
            attrs = pi.GetCustomAttributes(typeof(BrowsableAttribute), false);
            if (attrs.Length == 1)
            {
                BrowsableAttribute brAttr = (BrowsableAttribute)attrs[0];
                ret = brAttr.Browsable;
            }

            return ret;
        }

        /// <summary>
        /// Return true if provided property is XmlSerializable as determined by XmlIgnore attribute.
        /// </summary>
        /// <param name="pi">property info</param>
        /// <returns>true if XmlSerializable, false otherwise</returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static bool IsXmlSerializable(PropertyInfo pi)
        {
            bool ret = true;

            // Check for presence of XmlIgnore attribute.
            if (pi.GetCustomAttributes(typeof(XmlIgnoreAttribute), false).Length > 0)
            {
                ret = false;
            }
            return ret;
        }

        /// <summary>
        /// Returns short name of an assembly.  Useful for PackURI construction.
        /// </summary>
        /// <param name="assembly">assembly</param>
        /// <returns>assembly short name</returns>
        public static string GetAssemblyShortName(System.Reflection.Assembly assembly)
        {
            string assName = assembly.ManifestModule.Name;
            assName = assName.Substring(0, assName.LastIndexOf(".dll"));
            return assName;
        }

        #endregion
    }

    /// <summary>
    /// Helper class for storing PropertyInfo in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PropertyContainer<T> where T : class
    {
        public readonly PropertyInfo PropertyInfo;

        public PropertyContainer(PropertyInfo propertyInfo)
        {
            if (!propertyInfo.PropertyType.Equals(typeof(T)))
            {
                throw new ArgumentException("PropertyInfo type does not match template type.");
            }
            PropertyInfo = propertyInfo;
        }

        /// <summary>
        /// Retreive the value for the contained PropertyInfo attempting to create a default instance of the class.
        /// </summary>
        public T Value
        {
            get
            {
                if (_instance == null)
                {
                    _instance = Activator.CreateInstance(PropertyInfo.DeclaringType);
                }
                return GetPropertyValueForType(_instance);
            }
        }

        /// <summary>
        /// Retreive the value for the contained PropertyInfo using a provided instance.
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        public T GetPropertyValueForType(object instance)
        {
            if (instance != null)
            {
                return ReflectionHelper.GetObjectPropertyValue(instance, PropertyInfo) as T;
            }

            return null;
        }

        private object _instance;
    }
}
